package com.ivanceras.server.db;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Transaction;
import com.ivanceras.server.util.CStringUtils;
import com.ivanceras.server.util.DataTypeUtils;
import com.ivanceras.shared.db.DAO;
import com.ivanceras.shared.db.IDatabase;
import com.ivanceras.shared.db.Filter;
import com.ivanceras.shared.db.IModelDef;
import com.ivanceras.shared.db.Join;
import com.ivanceras.shared.db.Order;
import com.ivanceras.shared.exception.DatabaseException;

public class DB_BigTable implements IDatabase{

	DatastoreService datastore = null;
	Transaction txn = null;
	private boolean isTransacted;
	
	public DB_BigTable(String schema){
		System.out.println("Using "+DatabaseManager.BIGTABLE+" with schema: "+schema);
		datastore = DatastoreServiceFactory.getDatastoreService();
	}

	@Override
	public boolean createModel(IModelDef model) throws DatabaseException {
		return false;
	}


	@Override
	public DAO[] select(IModelDef model, Join join, Filter[] filters,
			Integer offset, Integer limit, Boolean doCount, Order[] orders)
	throws DatabaseException {
//		if(orders != null){
//			System.out.println(model.getModelName()+" Order[]: "+Arrays.asList(orders));
//		}
		long[] ids = selectIds(model, join, filters, offset, limit, orders);
		DAO[] recordObjs = new DAO[ids.length];
		String[] attributes = model.getAttributes();
		String[] dataTypes = model.getDataTypes();
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			recordObjs[i] = new DAO(model.getModelName());
			Entity entity;
			try {
				entity = datastore.get(key);
				for(int j = 0; j < attributes.length; j++){
					Object value = entity.getProperty(attributes[j]);
					recordObjs[i] = DataTypeUtils.set_DaoValue(recordObjs[i], attributes[j], value, dataTypes[CStringUtils.indexOf(attributes, attributes[j])]);
				}
			} catch (EntityNotFoundException e) {
				e.printStackTrace();
				throw new DatabaseException("Entity not found!");
			} 
		}
		return recordObjs;
	}


	
	@Override
	public int delete(IModelDef model, Filter[] filters)
	throws DatabaseException {
		NamespaceManager.set(model.getNamespace());
		long[] ids = selectIds(model, null, filters, null, null, null);
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			datastore.delete(key);
		}
		return ids.length;
	}

	@Override
	public boolean drop(IModelDef model) throws DatabaseException {
		NamespaceManager.set(model.getNamespace());
		empty(model);
		return true;
	}

	@Override
	public int empty(IModelDef model) throws DatabaseException {
		//TODO:
		throw new DatabaseException("not yet implemented");
	}


	HashMap<String, Entity> entityList = new HashMap<String, Entity>();
	
	@Override
	public DAO insert(DAO dao, IModelDef model) throws DatabaseException {
		String namespace = model.getNamespace();
//		if(namespace != null){
//			NamespaceManager.set(namespace);
//		}
		Entity entity = new Entity(model.getModelName());
		String[] attributes = model.getAttributes();
		for(int i = 0; i < attributes.length; i++){
			Object value = convertDaoValueToDB(dao, attributes[i]);
			entity.setProperty(attributes[i], value);
		}
		Key key = datastore.put(entity);
		dao.set_Value(model.getPrimaryColumn(),key);
		return dao;
	}

	@Override
	public DAO update(DAO dao, IModelDef model, Filter[] filters)
	throws DatabaseException {
		String namespace = model.getNamespace();
		if(namespace != null){
			NamespaceManager.set(namespace);
		}
		long[] ids = selectIds(model, null, filters, null, null, null);
		for(int i = 0; i < ids.length; i++){
			Key key = KeyFactory.createKey(model.getModelName(), ids[i]);
			Entity entity;
			try {
				entity = datastore.get(key);
				String[] attributes = model.getAttributes();
				for(int j = 0; j < attributes.length; j++){
					Object value = convertDaoValueToDB(dao, attributes[j]);
					entity.setProperty(attributes[j], value);
				}
				datastore.put(entity);
			} catch (EntityNotFoundException e) {
				e.printStackTrace();
				throw new DatabaseException("Entity not found!");
			}

		}
		return dao;
	}

	public long[] selectIds(IModelDef model, Join join, Filter[] filters, Integer offset, Integer limit, Order[] orders) throws DatabaseException {
		String namespace = model.getNamespace();
		if(namespace != null){
			NamespaceManager.set(namespace);
		}
		if(join != null){
			System.err.println("model 1: "+join.getFirstModel().getModelName());
			System.err.println("model 2: "+join.getForeignModel().getModelName());
		}
		Query q = new Query(model.getModelName());
		String[] attributes = model.getAttributes();
		String[] dataTypes = model.getDataTypes();
		if(filters != null){
			for(Filter f : filters){
				String[] filterColumn = f.attribute.split("\\.");
				String column = filterColumn[filterColumn.length-1];
				if(join != null){
					System.err.println(" "+filterColumn+" "+f.operator+" "+f.value);
				}
				String dataType = dataTypes[CStringUtils.indexOf(attributes, column)];
				q.addFilter(f.attribute, getEquivOp(f.operator), getFilterValue(f.value, dataType));
				if(orders != null && orders.length > 0){
					for(Order order : orders){
						String orderColumn = order.getColumn();
						SortDirection sortDirection = order.isAscending() ? SortDirection.ASCENDING : SortDirection.DESCENDING;
						System.out.println("order by "+orderColumn+" "+sortDirection.name());
						q.addSort(orderColumn, sortDirection);
					}
				}
			}
		}
		PreparedQuery pq = datastore.prepare(q);
		Iterator<Entity> results = null;
		if(limit != null && offset != null){
			results = pq.asIterator(FetchOptions.Builder.withLimit(limit).offset(offset));
		}else{
			results = pq.asIterator();
		}
		List<Long> ids = new ArrayList<Long>();
		while(results.hasNext()){
			Entity ent = results.next();
			long id = ent.getKey().getId();
			ids.add(id);
		}
		long ids_l[] = new long[ids.size()];
		for(int i = 0; i < ids.size(); i++){
			ids_l[i] = ids.get(i);
		}
		return ids_l;
	}

	private Query.FilterOperator getEquivOp(String operator){
		if(operator.equals(Filter.EQUAL)) return Query.FilterOperator.EQUAL;
		if(operator.equals(Filter.GREATER_THAN)) return Query.FilterOperator.GREATER_THAN;
		if(operator.equals(Filter.GREATER_THAN_OR_EQUAL)) return Query.FilterOperator.GREATER_THAN_OR_EQUAL;
		if(operator.equals(Filter.IN)) return Query.FilterOperator.IN;
		if(operator.equals(Filter.LESS_THAN)) return Query.FilterOperator.LESS_THAN;
		if(operator.equals(Filter.LESS_THAN_OR_EQUAL)) return Query.FilterOperator.LESS_THAN_OR_EQUAL;
		if(operator.equals(Filter.NOT_EQUAL)) return Query.FilterOperator.NOT_EQUAL;
		return Query.FilterOperator.EQUAL;
	}


	@Override
	public void beginTransaction() throws DatabaseException {
		txn = datastore.beginTransaction();
		isTransacted = false;
	}

	@Override
	public void commitTransaction() throws DatabaseException {
		txn.commit();
		isTransacted = true;
	}

	@Override
	public void rollbackTransaction() throws DatabaseException {
		txn.rollback();
		isTransacted = true;
	}

	@Override
	public boolean isTransacted() throws DatabaseException {
		return isTransacted;
	}

	@Override
	public boolean isClosed() {
		return datastore == null;
	}

	@Override
	public boolean reset() {
		return true;
	}


	@Override
	public Integer getTotalRecords(String schema, String modelName)
			throws DatabaseException {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public IModelDef getModelMetaData(String tableName) throws DatabaseException {
		// TODO Auto-generated method stub
		return null;
	}


//	@Override
//	public ResultSet executeSelectSQL(String sql, Object[] parameters)
//			throws DatabaseException {
//		// TODO Auto-generated method stub
//		return null;
//	}

	@Override
	public String[] getTableNames(String schema, String tablePattern)
			throws DatabaseException {
		throw new DatabaseException("This is not implemented in BigTable");
	}

	protected Object convertDaoValueToDB(DAO dao, String column){
		if(dao == null) return null;
		Object value = dao.get_Value(column);
		if(value == null){
			return null;
		}
		if(value.getClass().equals(java.math.BigDecimal.class)){
			return ((BigDecimal)value).doubleValue();
		}
		else if(value.getClass().equals(java.math.BigInteger.class)){
			return ((BigInteger)value).longValue();
		}
		else if(value.getClass().equals(java.lang.String.class) && value != null && ((String)value).length() > 500){
			Text text = new Text((String)value);
			return text;
		}
		else{
			return value;
		}
	}
	public static Object getFilterValue(Object value, String dataType){
		if(value == null){
			return null;
		}
		if(dataType.equals("java.math.BigDecimal")){
			return ((BigDecimal)value).doubleValue();
		}
		else if(dataType.equals("java.math.BigInteger")){
			return ((BigDecimal)value).longValue();
		}
		else if(value.getClass().equals(java.lang.String.class) && value != null && ((String)value).length() > 500){
			Text text = new Text((String)value);
			return text;
		}
		else{
			return value;
		}
	}
}